///<reference path="../../typings/index.d.ts" />
import * as path from 'path';
import _debug from 'debug';
const debug = _debug('core')
import {
  Contact,
  Contact_Data
} from "./interface/contact"
const FormData = require('form-data');
const mime = require('mime');
const bl = require('bl');

import {
  getCONF,
  Request,
  isStandardBrowserEnv,
  getClientMsgId,
  assert,
  getDeviceID
} from './util'

interface Action {
  type: string,
  action: string,
  time: number
}

interface Text {
  type: string,
  data: {
    actions: Action[]
  }
}

// interface coreContact {
//   UserName: string,
//   text: Text,
//   type: number
// }
interface SYKEY {
  Count: number;
  List: { Key: number, Val: number }[]
}
interface Prop {
  uuid: string,
  uin: string,
  sid: string,
  skey: string,
  passTicket: string,
  formatedSyncKey: string,
  webwxDataTicket: string,
  syncKey: SYKEY
}

enum OP_TYPE {
  cancel = 0, //取消置顶
  ding = 1 //联系人置顶
}
interface BOTDATA {
  PROP: Prop;
  CONF: any;
  COOKIE: any;
  user: Contact_Data;
}
interface baseResponse {
  Ret: number;
  ErrMsg: string;
}
export default class WechatCore {


  public PROP: Prop = {
    uuid: '',
    uin: '',
    sid: '',
    skey: '',
    passTicket: '',
    formatedSyncKey: '',
    webwxDataTicket: '',
    syncKey: {
      List: [],
      Count: 0
    }
  };
  public CONF: any = getCONF();
  private COOKIE = {};
  public user: Contact_Data = {
    "Uin": 0,
    "UserName": "",
    "NickName": "",
    "HeadImgUrl": "",
    "ContactFlag": 3,
    "MemberCount": 0,
    "MemberList": [],
    "RemarkName": "",
    "HideInputBarFlag": 0,
    "Sex": 0,
    "Signature": "",
    "VerifyFlag": 8,
    "OwnerUin": 0,
    "PYInitial": "",
    "PYQuanPin": "",
    "RemarkPYInitial": "",
    "RemarkPYQuanPin": "",
    "StarFriend": 0,
    "AppAccountFlag": 0,
    "Statues": 0,
    "AttrStatus": 0,
    "Province": "",
    "City": "",
    "Alias": "Urinxs",
    "SnsFlag": 0,
    "UniFriend": 0,
    "DisplayName": "",
    "ChatRoomId": 0,
    "KeyWord": "gh_",
    "EncryChatRoomId": ""
  };
  private requestIns: Request = null;
  private rediUri: string = '';
  constructor(data?: BOTDATA) {
    if (data) {
      this.botData = data;
    }
    this.requestIns = (new Request({ Cookie: this.COOKIE }));
  }
  public async request(options: any) {
    return await this.requestIns.request(options);
    // console.log(options.url);
    // let r: any = null;
    // try {
    //   r = 
    // } catch (err) {
    //   throw err;

    // }

    // if (r && r.data && r.data.BaseResponse) {
    //   if (r.data.BaseResponse.Ret != 0) {
    //     throw r;
    //   }
    // }
    // return r;
  }
  public get botData(): BOTDATA {
    return {
      PROP: this.PROP,
      CONF: this.CONF,
      COOKIE: this.COOKIE,
      user: this.user
    }
  }
  public set botData(data: BOTDATA) {
    if (data.hasOwnProperty("PROP")) {
      this.PROP = data.PROP;
    }
    if (data.hasOwnProperty("CONF")) {
      this.CONF = data.CONF;
    }
    if (data.hasOwnProperty("COOKIE")) {
      this.COOKIE = data.COOKIE;
    }
    if (data.hasOwnProperty("user")) {
      this.user = data.user;
    }
  }

  public async getUUID(): Promise<string> {
    let res;
    try {
      res = await this.request({
        method: 'POST',
        url: this.CONF.API_jsLogin
      });

      let window = {
        QRLogin: { uuid: '', code: '' }
      };
      // res.data: "window.QRLogin.code = xxx; ..."
      // eslint-disable-next-line
      eval(res.data);
      assert.equal(window.QRLogin.code, 200, res)
      this.PROP.uuid = window.QRLogin.uuid;
      return window.QRLogin.uuid;
    } catch (err) {
      debug(err)
      err.tips = '获取UUID失败';
      throw err;
    }
  }

  public async checkLogin() {
    let res;
    let params = {
      'tip': 0,
      'uuid': this.PROP.uuid,
      'loginicon': true
    };
    try {
      res = await this.request({
        method: 'GET',
        url: this.CONF.API_login,
        params: params
      });


      let window: any = {};
      // eslint-disable-next-line
      eval(res.data);
      assert.notEqual(window.code, 400, res)
      if (window.code === 200) {
        this.CONF = getCONF(window.redirect_uri.match(/(?:\w+\.)+\w+/)[0]);
        this.rediUri = window.redirect_uri;
      } else if (window.code === 201 && window.userAvatar) {
        // this.user.userAvatar = window.userAvatar
      } else if (window.code === 408) {

        // throw new Error('扫描超时');
      }
      return window;
    } catch (err) {
      debug(err)
      err.tips = '获取手机确认登录信息失败';
      throw err;
    }
  }

  public async login() {
    let res;
    try {
      res = await this.request({
        method: 'GET',
        url: this.rediUri,
        params: {
          fun: 'new'
        }
      });

      let pm = res.data.match(/<ret>(.*)<\/ret>/);
      if (pm && pm[1] === '0') {
        this.PROP.skey = res.data.match(/<skey>(.*)<\/skey>/)[1]
        this.PROP.sid = res.data.match(/<wxsid>(.*)<\/wxsid>/)[1]
        this.PROP.uin = res.data.match(/<wxuin>(.*)<\/wxuin>/)[1]
        this.PROP.passTicket = res.data.match(/<pass_ticket>(.*)<\/pass_ticket>/)[1]
      }
      if (res.headers['set-cookie']) {
        res.headers['set-cookie'].forEach((item: string) => {
          if (/webwx.*?data.*?ticket/i.test(item)) {
            this.PROP.webwxDataTicket = item.match(/=(.*?);/)[1]
          } else if (/wxuin/i.test(item)) {
            this.PROP.uin = item.match(/=(.*?);/)[1]
          } else if (/wxsid/i.test(item)) {
            this.PROP.sid = item.match(/=(.*?);/)[1]
          }
        })
      }
    } catch (err) {
      debug(err)
      err.tips = '登录失败';
      throw err;
    }
  }

  public async init(): Promise<any> {
    try {
      let res;
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'skey': this.PROP.skey,
        'r': ~new Date()
      }
      let reqData = {
        BaseRequest: this.getBaseRequest()
      }

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxinit,
        params: params,
        data: reqData
      });

      let data = res.data;
      assert.equal(data.BaseResponse.Ret, 0, res)
      this.PROP.skey = data.SKey || this.PROP.skey;
      this.updateSyncKey(data);
      Object.assign(this.user, data.User);
      return data;
    } catch (err) {
      debug(err)
      err.tips = '微信初始化失败';
      throw err;
    }

  }

  public async notifyMobile(to?: number) {
    try {
      let res;
      let params = {
        pass_ticket: this.PROP.passTicket,
        lang: 'zh_CN'
      }
      let reqData = {
        'BaseRequest': this.getBaseRequest(),
        'Code': to ? 1 : 3,
        'FromUserName': this.user['UserName'],
        'ToUserName': to || this.user['UserName'],
        'ClientMsgId': getClientMsgId()
      }

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxstatusnotify,
        params: params,
        data: reqData
      });
      let data = res.data;
      assert.equal(data.BaseResponse.Ret, 0, res)
    } catch (err) {
      debug(err)
      err.tips = '手机状态通知失败';
      throw err;
    }

  }


  public async getContact(seq: number = 0): Promise<{
    BaseResponse: baseResponse,
    MemberCount: number,
    MemberList: Contact_Data[],
    Seq: number
  }> {
    try {
      let res;
      let params = {
        'lang': 'zh_CN',
        'pass_ticket': this.PROP.passTicket,
        'seq': seq,
        'skey': this.PROP.skey,
        'r': +new Date()
      }

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxgetcontact,
        params: params
      });
      let data = res.data
      assert.equal(data.BaseResponse.Ret, 0, res)

      return data
    } catch (err) {
      debug(err)
      err.tips = '获取通讯录失败';
      throw err;
    }

  }

  public async batchGetContact(contacts: { UserName: string, EncryChatRoomId?: string }[]): Promise<Contact_Data[]> {
    try {
      let res;
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'type': 'ex',
        'r': +new Date(),
        'lang': 'zh_CN'
      };
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Count': contacts.length,
        'List': contacts
      };

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxbatchgetcontact,
        params: params,
        data: data
      });

      assert.equal(res.data.BaseResponse.Ret, 0, res)

      return res.data.ContactList
    } catch (err) {
      debug(err)
      err.tips = '批量获取联系人失败';
      throw err;
    }

  }

  public async statReport(text?: Text) {
    try {
      text = text || {
        'type': '[action-record]',
        'data': {
          'actions': [{
            'type': 'click',
            'action': '发送框',
            'time': +new Date()
          }]
        }
      }
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'fun': 'new',
        'lang': 'zh_CN'
      };
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Count': 1,
        'List': [{
          'Text': JSON.stringify(text),
          'Type': 1
        }]
      };


      return await this.request({
        method: 'POST',
        url: this.CONF.API_webwxreport,
        params: params,
        data: data
      });
    } catch (err) {
      debug(err)
      err.tips = '状态报告失败';
      throw err;
    }
  }


  public async syncCheck() {
    try {
      let res;
      let params = {
        'r': +new Date(),
        'sid': this.PROP.sid,
        'uin': this.PROP.uin,
        'skey': this.PROP.skey,
        'deviceid': getDeviceID(),
        'synckey': this.PROP.formatedSyncKey
      };

      res = await this.request({
        method: 'GET',
        url: this.CONF.API_synccheck,
        params: params
      });


      let window = {
        synccheck: { selector: {} }
      };
      // eslint-disable-next-line
      eval(res.data);
      assert.equal(window.synccheck["retcode"], this.CONF.SYNCCHECK_RET_SUCCESS, res)
      return window.synccheck.selector;
    } catch (err) {
      debug(err)
      err.tips = '同步失败';
      throw err;
    }
  }


  public async sync() {

    try {
      let res;
      let params = {
        'sid': this.PROP.sid,
        'skey': this.PROP.skey,
        'pass_ticket': this.PROP.passTicket,
        'lang': 'zh_CN'
      };
      let reqData = {
        'BaseRequest': this.getBaseRequest(),
        'SyncKey': this.PROP.syncKey,
        'rr': ~new Date()
      };

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxsync,
        params: params,
        data: reqData
      });


      let data = res.data;
      assert.equal(data.BaseResponse.Ret, 0, res)
      this.updateSyncKey(data);
      this.PROP.skey = data.SKey || this.PROP.skey;
      return data;
    } catch (err) {
      debug(err)
      err.tips = '获取新信息失败';
      throw err;
    }
  }

  public updateSyncKey(data: { SyncKey: SYKEY, SyncCheckKey: SYKEY }) {
    if (data.SyncKey) {
      this.PROP.syncKey = data.SyncKey
    }
    if (data.SyncCheckKey) {
      let synckeylist = []
      for (let e = data.SyncCheckKey.List, o = 0, n = e.length; n > o; o++) {
        synckeylist.push(e[o]['Key'] + '_' + e[o]['Val'])
      }
      this.PROP.formatedSyncKey = synckeylist.join('|')
    } else if (!this.PROP.formatedSyncKey && data.SyncKey) {
      let synckeylist = []
      for (let e = data.SyncKey.List, o = 0, n = e.length; n > o; o++) {
        synckeylist.push(e[o]['Key'] + '_' + e[o]['Val'])
      }
      this.PROP.formatedSyncKey = synckeylist.join('|')
    }
  }

  public async logout() {
    try {
      let params = {
        redirect: 1,
        type: 0,
        skey: this.PROP.skey,
        lang: 'zh_CN'
      };

      await this.request({
        method: 'POST',
        url: this.CONF.API_webwxlogout,
        params: params
      });
      return '登出成功';
    } catch (err) {
      debug(err)
      return '可能登出成功';
    }
  }

  public async sendText(msg: string, toUserName: string) {
    try {
      let res;
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'lang': 'zh_CN'
      };
      let clientMsgId = getClientMsgId();
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Scene': 0,
        'Msg': {
          'Type': this.CONF.MSGTYPE_TEXT,
          'Content': msg,
          'FromUserName': this.user['UserName'],
          'ToUserName': toUserName,
          'LocalID': clientMsgId,
          'ClientMsgId': clientMsgId
        }
      };


      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxsendmsg,
        params: params,
        data: data
      });

      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '发送文本信息失败';
      throw err;
    }

  }


  public async sendEmoticon(id: string, to: string) {
    try {
      let res;
      let params = {
        'fun': 'sys',
        'pass_ticket': this.PROP.passTicket,
        'lang': 'zh_CN'
      }
      let clientMsgId = getClientMsgId()
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Scene': 0,
        'Msg': {
          'Type': this.CONF.MSGTYPE_EMOTICON,
          'EmojiFlag': 2,
          'FromUserName': this.user['UserName'],
          'ToUserName': to,
          'LocalID': clientMsgId,
          'ClientMsgId': clientMsgId,
          'MediaId': "",
          'EMoticonMd5': ""
        }
      }
      if (id.indexOf('@') === 0) {
        data.Msg.MediaId = id;
      } else {
        data.Msg.EMoticonMd5 = id;
      }

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxsendemoticon,
        params: params,
        data: data
      });

      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '发送表情信息失败';
      throw err;
    }

  }

  // file: Stream, Buffer, File, Blob
  public async uploadMedia(file: any, filename: string, toUserName: string) {
    try {
      let name, type, size, ext, mediatype, data;
      if ((typeof (File) !== 'undefined' && file.constructor == File) || (typeof (Blob) !== 'undefined' && file.constructor == Blob)) {
        name = file.name || 'file';
        type = file.type;
        size = file.size;
        data = file;
      } else if (Buffer.isBuffer(file)) {
        if (!filename) {
          throw new Error('文件名未知');
        }
        name = filename;
        type = mime.lookup(name);
        size = file.length;
        data = file;
      } else if (file.readable) {
        if (!file.path && !filename) {
          throw new Error('文件名未知');
        }
        name = path.basename(file.path || filename);
        type = mime.lookup(name);
        await new Promise(function (resolve, reject) {
          file.pipe(bl((err: any, buffer: Buffer) => {
            if (err) {
              return reject(err)
            }
            size = buffer.length;
            data = buffer;
            return resolve();
          }));
        })
      }

      ext = name.match(/.*\.(.*)/)
      if (ext) {
        ext = ext[1].toLowerCase()
      } else {
        ext = ''
      }
      switch (ext) {
        case 'bmp':
        case 'jpeg':
        case 'jpg':
        case 'png':
          mediatype = 'pic';
          break;
        case 'mp4':
          mediatype = 'video';
          break;
        default:
          mediatype = 'doc';
      }
      let clientMsgId = getClientMsgId();
      let uploadMediaRequest = JSON.stringify({
        BaseRequest: this.getBaseRequest(),
        ClientMediaId: clientMsgId,
        TotalLen: size,
        StartPos: 0,
        DataLen: size,
        MediaType: 4,
        UploadType: 2,
        FromUserName: this.user.UserName,
        ToUserName: toUserName || this.user.UserName
      });
      let form = new FormData();
      form.append('name', name)
      form.append('type', type)
      form.append('lastModifiedDate', new Date().toUTCString())
      form.append('size', size)
      form.append('mediatype', mediatype)
      form.append('uploadmediarequest', uploadMediaRequest)
      form.append('webwx_data_ticket', this.PROP.webwxDataTicket)
      form.append('pass_ticket', encodeURI(this.PROP.passTicket))
      form.append('filename', data, {
        filename: name,
        contentType: type,
        knownLength: size
      });

      let result: any;
      if (isStandardBrowserEnv) {
        result = {
          data: form,
          headers: {}
        };
      } else {
        result = await new Promise(function (resolve, reject) {
          form.pipe(bl((err: any, buffer: Buffer) => {
            if (err) {
              return reject(err)
            }
            return resolve({
              data: buffer,
              headers: form.getHeaders()
            })
          }));
        });
      }

      let params = {
        f: 'json'
      };
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxuploadmedia,
        headers: result.headers,
        params: params,
        data: result.data
      });

      let mediaId = res.data.MediaId
      assert.ok(mediaId, res)

      return {
        name: name,
        size: size,
        ext: ext,
        mediatype: mediatype,
        mediaId: mediaId
      }
    } catch (err) {
      debug(err)
      err.tips = '上传媒体文件失败';
      throw err;
    }

  }


  public async sendPic(mediaId: string, to: string) {
    try {
      let res: any;
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'fun': 'async',
        'f': 'json',
        'lang': 'zh_CN'
      };
      let clientMsgId = getClientMsgId();
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Scene': 0,
        'Msg': {
          'Type': this.CONF.MSGTYPE_IMAGE,
          'MediaId': mediaId,
          'FromUserName': this.user.UserName,
          'ToUserName': to,
          'LocalID': clientMsgId,
          'ClientMsgId': clientMsgId
        }
      };


      res = this.request({
        method: 'POST',
        url: this.CONF.API_webwxsendmsgimg,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '发送图片失败';
      throw err;
    }

  }


  public async sendVideo(mediaId: string, to: string) {
    try {
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'fun': 'async',
        'f': 'json',
        'lang': 'zh_CN'
      };
      let clientMsgId = getClientMsgId();
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Scene': 0,
        'Msg': {
          'Type': this.CONF.MSGTYPE_VIDEO,
          'MediaId': mediaId,
          'FromUserName': this.user.UserName,
          'ToUserName': to,
          'LocalID': clientMsgId,
          'ClientMsgId': clientMsgId
        }
      };

      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxsendmsgvedio,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '发送视频失败';
      throw err;
    }

  }


  public async sendDoc(mediaId: string, name: string, size: string, ext: string, to: string) {
    try {
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'fun': 'async',
        'f': 'json',
        'lang': 'zh_CN'
      };
      let clientMsgId = getClientMsgId();
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Scene': 0,
        'Msg': {
          'Type': this.CONF.APPMSGTYPE_ATTACH,
          'Content': `<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>${name}</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl><appattach><totallen>${size}</totallen><attachid>${mediaId}</attachid><fileext>${ext}</fileext></appattach><extinfo></extinfo></appmsg>`,
          'FromUserName': this.user.UserName,
          'ToUserName': to,
          'LocalID': clientMsgId,
          'ClientMsgId': clientMsgId
        }
      };

      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxsendappmsg,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '发送文件失败';
      throw err;
    }

  }


  public async forwardMsg(msg: any, to: string) {
    try {
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'fun': 'async',
        'f': 'json',
        'lang': 'zh_CN'
      }
      let clientMsgId = getClientMsgId()
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Scene': 2,
        'Msg': {
          'Type': msg.MsgType,
          'MediaId': '',
          'Content': msg.Content.replace(/&lt;/g, '<').replace(/&gt;/g, '>'),
          'FromUserName': this.user.UserName,
          'ToUserName': to,
          'LocalID': clientMsgId,
          'ClientMsgId': clientMsgId,
          'EMoticonMd5': "",
          'EmojiFlag': 0
        }
      }
      let url, pm
      switch (msg.MsgType) {
        case this.CONF.MSGTYPE_TEXT:
          url = this.CONF.API_webwxsendmsg
          if (msg.SubMsgType === this.CONF.MSGTYPE_LOCATION) {
            data.Msg.Type = this.CONF.MSGTYPE_LOCATION
            data.Msg.Content = msg.OriContent.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
          }
          break
        case this.CONF.MSGTYPE_IMAGE:
          url = this.CONF.API_webwxsendmsgimg
          break
        case this.CONF.MSGTYPE_EMOTICON:
          url = this.CONF.API_webwxsendemoticon
          params.fun = 'sys'
          data.Msg.EMoticonMd5 = msg.Content.replace(/^[\s\S]*?md5\s?=\s?"(.*?)"[\s\S]*?$/, '$1')
          if (!data.Msg.EMoticonMd5) {
            throw new Error('商店表情不能转发')
          }
          data.Msg.EmojiFlag = 2
          data.Scene = 0
          delete data.Msg.MediaId
          delete data.Msg.Content
          break
        case this.CONF.MSGTYPE_MICROVIDEO:
        case this.CONF.MSGTYPE_VIDEO:
          url = this.CONF.API_webwxsendmsgvedio
          data.Msg.Type = this.CONF.MSGTYPE_VIDEO
          break
        case this.CONF.MSGTYPE_APP:
          url = this.CONF.API_webwxsendappmsg
          data.Msg.Type = msg.AppMsgType
          data.Msg.Content = data.Msg.Content.replace(
            /^[\s\S]*?(<appmsg[\s\S]*?<attachid>)[\s\S]*?(<\/attachid>[\s\S]*?<\/appmsg>)[\s\S]*?$/,
            `$1${msg.MediaId}$2`)
          break
        default:
          throw new Error('该消息类型不能直接转发')
      };

      let res;

      res = await this.request({
        method: 'POST',
        url: url,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '转发消息失败';
      throw err;
    }

  }



  public async getMsgImg(msgId: string) {
    try {
      let res;
      let params = {
        MsgID: msgId,
        skey: this.PROP.skey,
        type: 'big'
      };

      res = await this.request({
        method: 'GET',
        url: this.CONF.API_webwxgetmsgimg,
        params: params,
        responseType: 'arraybuffer'
      });
      return {
        data: res.data,
        type: res.headers['content-type']
      }
    } catch (err) {
      debug(err)
      err.tips = '获取图片或表情失败';
      throw err;
    }

  }


  public async getVideo(msgId: string) {
    try {
      let res;
      let params = {
        MsgID: msgId,
        skey: this.PROP.skey
      };

      res = await this.request({
        method: 'GET',
        url: this.CONF.API_webwxgetvideo,
        headers: {
          'Range': 'bytes=0-'
        },
        params: params,
        responseType: 'arraybuffer'
      });
      return {
        data: res.data,
        type: res.headers['content-type']
      }
    } catch (err) {
      debug(err)
      err.tips = '获取视频失败';
      throw err;
    }

  }


  public async getVoice(msgId: string) {
    try {
      let res;
      let params = {
        MsgID: msgId,
        skey: this.PROP.skey
      };

      res = await this.request({
        method: 'GET',
        url: this.CONF.API_webwxgetvoice,
        params: params,
        responseType: 'arraybuffer'
      });
      return {
        data: res.data,
        type: res.headers['content-type']
      };
    } catch (err) {
      debug(err)
      err.tips = '获取声音失败';
      throw err;
    }

  }


  public async getHeadImg(HeadImgUrl: string) {
    let url = this.CONF.origin + HeadImgUrl;
    let res;
    try {
      res = await this.request({
        method: 'GET',
        url: url,
        responseType: 'arraybuffer'
      });
      return {
        data: res.data,
        type: res.headers['content-type']
      };
    } catch (err) {
      debug(err)
      err.tips = '获取头像失败';
      throw err;
    }

  }


  public async getDoc(FromUserName: string, MediaId: string, FileName: string) {
    try {
      let res;
      let params = {
        sender: FromUserName,
        mediaid: MediaId,
        filename: FileName,
        fromuser: this.user.UserName,
        pass_ticket: this.PROP.passTicket,
        webwx_data_ticket: this.PROP.webwxDataTicket
      };

      res = await this.request({
        method: 'GET',
        url: this.CONF.API_webwxdownloadmedia,
        params: params,
        responseType: 'arraybuffer'
      });
      return {
        data: res.data,
        type: res.headers['content-type']
      }
    } catch (err) {
      debug(err)
      err.tips = '获取文件失败';
      throw err;
    }

  }


  public async verifyUser(UserName: string, Ticket: string) {
    try {
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'lang': 'zh_CN'
      }
      let data = {
        'BaseRequest': this.getBaseRequest(),
        'Opcode': 3,
        'VerifyUserListSize': 1,
        'VerifyUserList': [{
          'Value': UserName,
          'VerifyUserTicket': Ticket
        }],
        'VerifyContent': '',
        'SceneListCount': 1,
        'SceneList': [33],
        'skey': this.PROP.skey
      };
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxverifyuser,
        params: params,
        data: data
      });

      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '通过好友请求失败';
      throw err;
    }

  }


  public async addFriend(UserName: string, content: string = '我是' + this.user.NickName) {
    let res;
    let params = {
      'pass_ticket': this.PROP.passTicket,
      'lang': 'zh_CN'
    };
    let data = {
      'BaseRequest': this.getBaseRequest(),
      'Opcode': 2,
      'VerifyUserListSize': 1,
      'VerifyUserList': [{
        'Value': UserName,
        'VerifyUserTicket': ''
      }],
      'VerifyContent': content,
      'SceneListCount': 1,
      'SceneList': [33],
      'skey': this.PROP.skey
    };
    try {
      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxverifyuser,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data
    } catch (err) {
      debug(err)
      err.tips = '添加好友失败';
      throw err;
    }

  }

  // Topic: Chatroom name
  // MemberList format:
  // [
  //   {"UserName":"@250d8d156ad9f8b068c2e3df3464ecf2"},
  //   {"UserName":"@42d725733741de6ac53cbe3738d8dd2e"}
  // ]
  public async createChatroom(Topic: string, MemberList: { UserName: string }[]) {
    try {
      let params = {
        'pass_ticket': this.PROP.passTicket,
        'lang': 'zh_CN',
        'r': ~new Date()
      };
      let data = {
        BaseRequest: this.getBaseRequest(),
        MemberCount: MemberList.length,
        MemberList: MemberList,
        Topic: Topic
      };
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxcreatechatroom,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data;
    } catch (err) {
      debug(err)
      err.tips = '创建群失败';
      throw err;
    }

  }

  // fun: 'addmember' or 'delmember' or 'invitemember'
  public async updateChatroom(ChatRoomUserName: string, MemberList: any[], fun: string) {
    try {
      let params = {
        fun: fun
      };
      let data = {
        BaseRequest: this.getBaseRequest(),
        ChatRoomName: ChatRoomUserName,
        AddMemberList: "",
        DelMemberList: "",
        InviteMemberList: ""
      }
      if (fun === 'addmember') {
        data.AddMemberList = MemberList.toString()
      } else if (fun === 'delmember') {
        data.DelMemberList = MemberList.toString()
      } else if (fun === 'invitemember') {
        data.InviteMemberList = MemberList.toString()
      }
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxupdatechatroom,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data;
    } catch (err) {
      debug(err)
      err.tips = '邀请或踢出群成员失败';
      throw err;
    }

  }

  // OP: 1 联系人置顶 0 取消置顶
  // 若不传RemarkName，则会覆盖以设置的联系人备注名
  public async opLog(UserName: string, OP: OP_TYPE, RemarkName?: string) {
    try {
      let params = {
        pass_ticket: this.PROP.passTicket
      };
      let data = {
        BaseRequest: this.getBaseRequest(),
        CmdId: 3,
        OP: OP,
        RemarkName: RemarkName,
        UserName: UserName
      };
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxoplog,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data;
    } catch (err) {
      debug(err)
      err.tips = '置顶或取消置顶失败';
      throw err;
    }

  }


  public async updateRemarkName(UserName: string, RemarkName: string) {
    try {
      let params = {
        pass_ticket: this.PROP.passTicket,
        'lang': 'zh_CN'
      };
      let data = {
        BaseRequest: this.getBaseRequest(),
        CmdId: 2,
        RemarkName: RemarkName,
        UserName: UserName
      };
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxoplog,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data;
    } catch (err) {
      debug(err)
      err.tips = '设置用户标签失败';
      throw err;
    }

  }


  public async updateChatRoomName(ChatRoomUserName: string, NewName: string) {
    try {
      let params = {
        'fun': 'modtopic'
      };
      let data = {
        BaseRequest: this.getBaseRequest(),
        ChatRoomName: ChatRoomUserName,
        NewTopic: NewName
      };

      let res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxupdatechatroom,
        params: params,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
    } catch (err) {
      debug(err)
      throw new Error('更新群名失败')
    }

  }


  public async revokeMsg(msgId: string, toUserName: string) {
    try {
      let data = {
        BaseRequest: this.getBaseRequest(),
        SvrMsgId: msgId,
        ToUserName: toUserName,
        ClientMsgId: getClientMsgId()
      };
      let res;

      res = await this.request({
        method: 'POST',
        url: this.CONF.API_webwxrevokemsg,
        data: data
      });
      assert.equal(res.data.BaseResponse.Ret, 0, res)
      return res.data;
    } catch (err) {
      debug(err)
      throw new Error('撤回消息失败');
    }

  }


  public getBaseRequest() {
    return {
      Uin: parseInt(this.PROP.uin),
      Sid: this.PROP.sid,
      Skey: this.PROP.skey,
      DeviceID: getDeviceID()
    }
  }
}